package edu.vt.ece.rt.quorum;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.jgroups.Address;
import org.jgroups.protocols.TP;

import edu.vt.ece.rt.quorum.tree.Node;
import edu.vt.ece.rt.quorum.util.DistanceFinder;
import edu.vt.ece.rt.quorum.util.Names;
import edu.vt.ece.rt.quorum.util.Nodex;
import edu.vt.ece.rt.quorum.util.WaitObject;
import edu.vt.ece.rt.util.Logger.Logger;

/*
 * Allow Node to access the different Clusters
 * @author Sudhanshu
 */
public class ClustersManager {	
	/*
	 * List of Clusters of which given node is member of, Just for persistence, so GC don't collect
	 */
	static ConcurrentHashMap<String, Cluster> clusterMap = new ConcurrentHashMap<String, Cluster>();
	
	public static TP tp = null;
	/*
	 * Concurrent Map of Address and its details : node, status, distance value
	 */
	public static ConcurrentHashMap<Address, Nodex> distanceMap;
	/*
	 * Wait Point for the Object to calculate the wait cost
	 */
	public static AtomicInteger counter;
	/*
	 * Should be set on basis of node address in whole list (0-n)
	 * It is set by base Cluster setId call
	 */
	public static int baseClusterId;		//AKA NodeId
	
	/*
	 * A wait object to synchronise the exit time of all the nodes
	 */
	public static WaitObject waitTillDone;
	
	/*
	 * Cluster wait time, after which responding is considered to dead
	 */
	public final static Long ClusterWaitTime = Long.getLong(Names.clusterWaitTime); 
	
	static int nodeCount=0;
	
	public static void registerCluster(String name, Cluster c){
		clusterMap.put(name, c);
	}

	public static Cluster getCluster(String name) {
		return clusterMap.get(name);		
	}
	
	public static void createEntry(Address dest){
		distanceMap.get(dest).setDistance(System.currentTimeMillis());
	}
	
	public static void getdistance(Address dest){
		AtomicBoolean waitPoint = distanceMap.get(dest).getWaitPoint();
		synchronized (waitPoint) {
			Long d = System.currentTimeMillis() - distanceMap.get(dest).getdistance();
			distanceMap.get(dest).setDistance(d);
			waitPoint.compareAndSet(false, true);		//Used to verify failure
			waitPoint.notifyAll();
		}
	}
	
	/*
	 * Blocks until all the distances to node are not calculated
	 */
	public static void calculateDistance( ){
		if(distanceMap == null){
			//Create distance map
			distanceMap = new ConcurrentHashMap<Address, Nodex>();
			List<Node> l = ClustersManager.getCluster(Names.base).nodeList;
			for(Node n:l){
				distanceMap.put(n.address, new Nodex(n));
			}
		}
		
		Logger.debug("Starting the Distance Calculation");
		//Set AutoReceive on Base Cluster On
		ClustersManager.getCluster(Names.base).setAutoReceive(true);
		ArrayList<Thread> lst = new ArrayList<Thread>();
		for(Node n: ClustersManager.getCluster(Names.base).nodeList){
			DistanceFinder df = new DistanceFinder(n.address);
			Thread t = new Thread(df);
			t.start();
			lst.add(t);
		}		
		
		Logger.debug("Waiting distance Request Responses");
		for(Thread t: lst){
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		ClustersManager.getCluster(Names.base).setAutoReceive(false);
	}
	
	public static final ConcurrentHashMap<String, Cluster> getClusterMap(){
		return clusterMap;
	}

	/**Provides the count of nodes initially provided
	 * @return
	 */
	public static int nodesCount() {
		if(nodeCount==0)
			nodeCount =Integer.getInteger(Names.nodes);
		return nodeCount;
	}
}
